package sk.datalan.similarity;

import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.iterator.RandomIter;
import javax.media.jai.iterator.RandomIterFactory;

import sk.datalan.similarity.predicates.Image;

public final class ImageSimilarityProcessor {

	private static final int baseSize = 360; // resolution of rescaled image
	private static ImageSimilarityProcessor processor = new ImageSimilarityProcessor();

	public ImageSimilarityProcessor() {
	}

	public static ImageSimilarityProcessor getInstance() {
		return processor;
	}

	/*
	 * This method rescales an image to baseSize x baseSize pixels using the JAI scale
	 * operator.
	 */
	private RenderedImage rescale( RenderedImage i ) {
		float scaleW = ( (float) baseSize ) / i.getWidth();
		float scaleH = ( (float) baseSize ) / i.getHeight();
		// Scales the original image
		ParameterBlock pb = new ParameterBlock();
		pb.addSource( i );
		pb.add( scaleW );
		pb.add( scaleH );
		pb.add( 0.0F );
		pb.add( 0.0F );
		pb.add( new InterpolationNearest() );
		// Creates a new, scaled image and uses it on the DisplayJAI component
		return JAI.create( "scale", pb );
	}

	
	/*
	 * Calculate image hash for input image
	 */
	public String calculateImageHash( URL url ) {
		RenderedImage image;
		try {
			image = rescale( ImageIO.read( url ) );
			return calcHash( image );
		} catch ( IOException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null;
	}

	
	/*
	 * Calculate image hash for input image
	 */
	public String calculateImageHash( File file ) {
		RenderedImage image;
		try {
			image = rescale( ImageIO.read( file ) );
			return calcHash( image );
		} catch ( IOException e ) {
			// TODO Auto-generated catch block
			System.out.println( file.getAbsolutePath() );
			e.printStackTrace();
		}

		return null;
	}

	/*
	 * This method calculates and returns hash for the input image.
	 */
	private String calcHash( RenderedImage i ) {

		StringBuffer sig = new StringBuffer( 1000 );


		double[] prop = new double[] { 0.1, 0.25, 0.4, 0.6, 0.75, 0.9 };
		for ( int x = 0; x < Image.XSIDE; x++ ) {
			for ( int y = 0; y < Image.XSIDE; y++ ) {
				sig.append( averageAround( i, prop[x], prop[y] ) );
				sig.append( ";" );
			}
		}
		return sig.toString();
	}

	/*
	 * This method averages the pixel values around a central point and return
	 * the average as hash string. The point coordinates are
	 * proportional to the image.
	 */
	private String averageAround( RenderedImage i, double px, double py ) {
		// Get an iterator for the image.
		RandomIter iterator = RandomIterFactory.create( i, null );
		// Get memory for a pixel and for the accumulator.
		double[] pixel = new double[3];
		double[] accum = new double[3];
		// The size of the sampling area.
		int sampleSize = 15;
		int numPixels = 0;
		// Sample the pixels.
		for ( double x = px * baseSize - sampleSize; x < px * baseSize + sampleSize; x++ ) {
			for ( double y = py * baseSize - sampleSize; y < py * baseSize + sampleSize; y++ ) {
				iterator.getPixel( (int) x, (int) y, pixel );
				accum[0] += pixel[0];
				accum[1] += pixel[1];
				accum[2] += pixel[2];
				numPixels++;
			}
		}
		// Average the accumulated values.
		accum[0] /= numPixels;
		accum[1] /= numPixels;
		accum[2] /= numPixels;
		return Integer.toHexString( ( (int) ( Math.pow( 256, 2 ) * (int) accum[0] ) + ( 256 * (int) accum[1] ) + (int) accum[2] ) );
	}

}